local ffi = require "ffi"
local jit = require('jit')

--计算缓存权重得分
--arr保存时间戳数组，最近的放后面
--latest最新时间戳
--maxAge最新时间和访问时间戳的最大差值，差值超过这个限额不计算缓存权重
--t1,t2,t3：t2和t1相减为t2的瞬时访问间隔，间隔越小，频率越大，缓存权重越大
--          latest和t2相减为t2的访问年龄，年龄越小，缓存权重越大
local function calcCacheWeight(arr, latest, maxAge)
    local score = 0
    for index, item in ipairs(arr) do
        local age = latest - item --年龄，年龄为0的缓存权重最大
        if age > maxAge then
            age = maxAge
        end
        local ageScore = (math.exp(tonumber((maxAge - age) / maxAge))-1) --年龄得分
        local frequencyScore = 0
        if 1 < index then
            local space = item - arr[index-1] --访问间隔，间隔为0的频率最大，缓存权重越大
            frequencyScore = (math.exp(tonumber((maxAge - space) / maxAge))-1) --频率得分
        end
        score = score + ageScore + frequencyScore
    end
    return score
end

--重新计算缓存权重排名，并且缓存或者清除缓存
--rankMap 排名map
--maxCount 排名最大数量
--key 资源的key
--calcCacheWeightFn 资源缓存权重的计算函数
--cacheFn 缓存操作函数
--        第1个参数为bool型，true表示添加缓存，false表示删除缓存
--        第2个参数为资源的key
local function calcCacheRank(rankMap, maxCount, key, calcCacheWeightFn, cacheFn)
    --重新计算已有排名的缓存权重，把分数为0的剔除出排名，并删除缓存
    for k, v in pairs(rankMap) do
        local newScore = calcCacheWeightFn(k) --重新计算缓存权重
        rankMap[k] = newScore
        if 0 >= newScore then --分数为0的，删排名删缓存
            rankMap[k] = nil
            cacheFn(false, k)
        end
    end

    --计算当前访问资源的缓存权重，分数为0的，不做任何处理，直接返回
    local score = calcCacheWeightFn(key)
    if 0 >= score then
        return
    end

    --把当前访问资源加入排名，加入缓存
    rankMap[key] = score
    cacheFn(true, key)

    --找出当前排名中缓存权重最小的那个
    local minScore = score
    local keyOfMinScore = key
    local count = 0
    for k, v in pairs(rankMap) do
        count=count+1
        if v < minScore then
            keyOfMinScore = k
            minScore = v
        end
    end

    --如果排名超出容量，则把排名最小的剔除，并删除对应的缓存
    if count > maxCount then
        rankMap[keyOfMinScore] = nil
        cacheFn(false, keyOfMinScore)
    end
end

--重复声明会报错，用pcall屏蔽错误
if "Windows" == jit.os then
    pcall(function ()
        ffi.cdef[[
            typedef unsigned short WORD;
            typedef struct _SYSTEMTIME {
                WORD wYear;
                WORD wMonth;
                WORD wDayOfWeek;
                WORD wDay;
                WORD wHour;
                WORD wMinute;
                WORD wSecond;
                WORD wMilliseconds;
            } SYSTEMTIME;
            void GetLocalTime(SYSTEMTIME * stUTC);
        ]]
    end)
else
    pcall(function ()
        ffi.cdef[[
            struct timezone{
                int tz_minuteswest;
                int tz_dsttime;
            };
            struct timeval {
                long tv_sec; /* seconds */
                long tv_usec; /* microseconds */
            };
            int gettimeofday(struct timeval *tv, struct timezone *tz);
        ]]
    end)
end

--获取当前时间的毫秒数
local function getCurrTime()
    if "Windows" == jit.os then
        local kernel32 = ffi.load("kernel32")
        local tm = ffi.new("SYSTEMTIME")
        kernel32.GetLocalTime(tm)
        local seconds = os.time({year=tm.wYear,month=tm.wMonth,day=tm.wDay,hour=tm.wHour,min=tm.wMinute,sec=tm.wSecond})
        local timemillis = seconds * 1000 + tm.wMilliseconds
        return timemillis
    else
        local tm = ffi.new("struct timeval")
        local tzone = ffi.new("struct timezone")
        ffi.C.gettimeofday(tm, tzone)
        local timemillis = tm.tv_sec * 1000 + tm.tv_usec/1000
        return timemillis
    end
end

local genCacheAction = function (inst, data)
    local cacheAction = function (store, key)
        if store then
            inst.cacheMap[key] = data
        else
            inst.cacheMap[key] = nil
        end
    end
    return cacheAction
end

--添加访问记录
local addAccessRecord = function (inst, key, time)
    local accessLog = inst.accessLogMap[key]
    if not accessLog then
        accessLog = {}
        inst.accessLogMap[key] = accessLog
    end
    table.insert(accessLog, time)
    while(inst.maxLogCount < table.getn(accessLog))
    do
       table.remove(accessLog, 1)
    end
end

local genCalcCacheWeightFn = function (inst, latest)
    local calcWeight = function (key)
        local accessLog = inst.accessLogMap[key]
        if not accessLog then
            return 0
        end
        return calcCacheWeight(accessLog, latest, inst.maxAge)
    end
    return calcWeight
end

local getCache = function (inst, key)
    return inst.cacheMap[key]
end

local delCache = function (inst, key)
    inst.cacheMap[key] = nil
end

local getInstance = function (maxLogCount, maxAge, maxRankCount)
    local inst = {}
    inst.rankMap = {}
    inst.accessLogMap = {}
    inst.cacheMap = {}
    inst.maxLogCount = maxLogCount
    inst.maxAge = maxAge
    inst.maxRankCount = maxRankCount
    inst.getCache = function (key)
        return getCache(inst, key)
    end
    inst.delCache = function (key)
        delCache(inst, key)
    end
    inst.addAccessRecord = function (key, time)
        addAccessRecord(inst, key, time)
    end
    inst.calcCacheRank = function (key, time, data)
        calcCacheRank(inst.rankMap, inst.maxRankCount, key, genCalcCacheWeightFn(inst, time), genCacheAction(inst, data))
    end

    return inst
end

local M = {}
M.new = getInstance
M.getCurrTime = getCurrTime

local moduleName = ...
_G[moduleName] = M
complex = M
return complex